#!/usr/bin/env node
process.title = 'mediasoup-demo-server';
//process.env.DEBUG = process.env.DEBUG || '*INFO* *WARN* *ERROR*';
process.env.DEBUG = '*INFO* *WARN*  *ERROR*';

// 引入配置模块
const config = require('./config');

/* eslint-disable no-console */
console.log('process.env.DEBUG:', process.env.DEBUG);
console.log('config.js:\n%s', JSON.stringify(config, null, '  '));
/* eslint-enable no-console */

// 引入日志
const Logger = require('./lib/Logger');
// 引入mediasoup
const mediasoup = require("mediasoup");
// 引入房间模块
const Room = require('./lib/Room');
// 引入https 模块为后面构建httpsServer以及websocket做铺垫
const https = require('https');
// 引入 protoo websocket模块,而protoo-server 依赖https
const protooServer = require('protoo-server');
// 引入文件系统
const fs = require('fs');
// 引入url模块，用于解析websocket url上的参数
const url = require('url');
// 引入等待队列模块异步的，主要目的是防止在同一时间回创建多个room房间
const { AwaitQueue } = require('awaitqueue');
// 实例化队列对象
const queue = new AwaitQueue();
// 实例化日志对象
const logger = new Logger();

// 定义worker的存储
const workers = [];
// 定义房间的存储;注意：可以使用redis来存储
const rooms = new Map();
// 定义httpServer对象
let httpsServer;
// 定义protooWebsocket对象
let protooWebsocket;
// 定义下一个使用的worker所在的索引;默认0
let nextWorkerIndex = 0;

// main方法,程序入口，同步调用各个方法
(async () => {
  logger.info('=======================mediasoup正在启动,版本号::V%s...=======================', mediasoup.version);
  // 监听worker的创建
  mediasoup.observer.on("newworker", (worker) => {
    logger.info('new mediasoup worker【workerId:%d】被创建', worker.pid);
  });
  // 初始化worker
  await initWorkers();
  // 初始化httpsServer
  await initHttpsServer();
  // 初始化protooWebsocket
  await initWebsocket();
  logger.info('=======================mediasoup启动完成=======================');
  // 定时隔一段事件打印出room房间中的信息
  setInterval(() => {
    for (const room of rooms.values()) {
      room.logStatus();
    }
  }, 600000);
})();

// 初始化mediasoup worker
async function initWorkers () {
  logger.info('=======================mediasoup Worker正在初始化...=======================');
  // 获取配置文件中的需要创建的worker个数，并创建workers
  for (let i = 0; i < config.mediasoup.numWorkers; ++i) {
    // 创建worker
    const worker = await mediasoup.createWorker(config.mediasoup.workerSettings);
    // 给worker绑定监听事件
    worker.on('died', (error) => {
      logger.error('mediasoup worker died!,2秒后进程将退出,异常与原因: 【%o】', error);
      setTimeout(() => {
        process.exit(1);
      }, 2000);
    });

    // 处理观察者事件
    // worker 关闭了
    worker.observer.on('close', () => {
      logger.error('mediasoup worker【workerId:%s】 正在关闭...', worker.pid);
    })
    // 观察新的router路由创建
    worker.observer.on('newrouter', (router) => {
      logger.info('mediasoup worker 【workerId:%s】 上创建了一个新的router 【routerId:%s】', worker.pid, router.id);
    });
    // 将worker存入数组保存起来
    workers.push(worker);
    // Log worker resource usage every 120 seconds.
    setInterval(async () => {
      const usage = await worker.getResourceUsage();
      logger.info('mediasoup Worker 资源使用情况: 【workerId:%d】: 【usage:%o】', worker.pid, usage);
    }, 600000);
  }
  logger.info('=======================mediasoup Worker初始化完成=======================');

}

// 初始化httpsServer 服务
async function initHttpsServer () {
  logger.info('=======================httpsServer正在初始化...=======================');
  // 构建httpsServer的参数
  const options = {
    key: fs.readFileSync(config.https.tls.key),
    cert: fs.readFileSync(config.https.tls.cert)
  }
  httpsServer = https.createServer(options);
  await new Promise((resolve) => {
    logger.info(`protooWebsocket 监听的地址:【${config.https.host}:${config.https.port}】`);
    httpsServer.listen(Number(config.https.port), config.https.host, resolve);
  });
  logger.info('=======================httpsServer初始化完成=======================');

}

// 初始化websocket
async function initWebsocket () {
  logger.info('=======================websocket正在初始化...=======================');
  // 构建参数
  const options = {
    maxReceivedFrameSize: 960000, // 960 KBytes.允许的最大接收帧大小（以字节为单位）。单帧消息也将限于此最大值。
    maxReceivedMessageSize: 960000,//允许的最大消息大小（对于分段消息），以字节为单位。
    fragmentOutgoingMessages: true,//是否对传出消息进行分段。如果为true，则邮件将自动分成最大为fragmentationThreshold字节的块
    fragmentationThreshold: 960000//在自动分段之前，帧的最大大小（以字节为单位）。
  }
  protooWebsocket = new protooServer.WebSocketServer(httpsServer, options);
  // ===============监听相关事件===========
  // 当有请求连接时触发这里
  protooWebsocket.on('connectionrequest', (info, accept, reject) => {
    logger.info('开始 websocket connectionrequest 连接请求...');
    // 做相关处理
    // 解析info中的数据解析出url中的参数roomId,peerId
    const websocketUrl = url.parse(info.request.url, true);
    const roomId = websocketUrl.query['roomId'];
    const peerId = websocketUrl.query['peerId'];
    logger.info('websocket request url:【websocketUrl:%o】', websocketUrl);
    // 判断参数是否正确
    if (!roomId || !peerId) {
      logger.error('非法参数错误！【url:%s】', websocketUrl);
      reject(400, 'url连接中没有roomId或peerId');
    }
    // 创建或获取房间,在任务task队列中
    queue.push(async () => {
      const room = await getOrCreateRoom({ roomId });
      // 接受连接,返回连接通道,是否是异步？
      const webSocketTransport = accept();
      // 处理连接请求,这里可以测试是否服务器端和客户端是异步的
      // for (var i = 0; i < 100000; i++) {
      //   logger.info('================>开始等待处理请求：%d', i);
      // }
      room.handleConnectionRequest({ webSocketTransport, peerId });
    }).catch((error) => {
      logger.error('创建或获取房间失败！【原因:%o】', error);
      reject(error);
    });
  });
  logger.info('=======================websocket初始化完成=======================');

}
// 创建或获取房间
async function getOrCreateRoom ({ roomId }) {
  logger.info('根据roomId【roomId:%s】开始获取或创建room...', roomId);
  // 先判断该roomId是否存在，如果存在则直接返回存在的房间
  let room = rooms.get(roomId);
  if (room) {
    return room;
  }
  // 获取worker
  const worker = getWorker(nextWorkerIndex);
  // 在指定的worker上创建room
  room = await Room.create({ worker, roomId });
  // 将room 存储起来
  rooms.set(roomId, room);
  // 绑定room关闭的监听事件
  room.on('close', () => {
    logger.error('正在关闭room【roomId:%s】...', roomId);
    rooms.delete(roomId);
  });
  return room;
}

// 根据索引获取worker
function getWorker (workerIndex) {
  logger.info('正在根据workerIndex【workerIndex:%d】获取worker', workerIndex);
  try {
    const worker = workers[workerIndex];
    // 将workerIndex 加1
    if (++workerIndex === workers.length) {
      nextWorkerIndex = 0;
    }
    return worker;
  } catch {
    logger.error('获取worker失败！【workerIndex: %d】', workerIndex);
    throw new Error('获取worker失败!');
  }

}

